package reit.ass3;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

import org.apache.log4j.Logger;

import reit.parser.cutomergroups.CustomersGroups;
import reit.parser.cutomergroups.CustomersGroups.CustomerGroups;
import reit.parser.initialdata.REIT;
import reit.parser.initialdata.REIT.Staff.Clerks.Clerk;
import reit.parser.initialdata.REIT.Warehouse.Materials.Material;
import reit.parser.initialdata.REIT.Warehouse.Tools.Tool;

import com.google.common.collect.ArrayListMultimap;


/**
 * The Class Parser, parses XML files for building Managment and Warehouse
 */
public class Parser {
	private static Logger fLogger = Logger.getLogger(Parser.class);

	/**
	 * Instantiates a new parser.
	 * This exists only in order to defeat correct javadoc
	 */
	public Parser(){
		
	}
	
	/**
	 * Creates the clerks.
	 *
	 * @param path to xml file containing staff (initalData.xml)
	 * @return the list
	 * @pre created JAXB classes for XML file
	 * @post returns Hashtable of ClerkDetails sorted by thier name as appear in xml
	 * @inv 
	 */
	public static List<ClerkDetails> createClerks(final String path) {
		fLogger.info("In CreateClerks");
		ArrayList<ClerkDetails> result = new ArrayList<ClerkDetails>();
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.initialdata.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			reit.parser.initialdata.REIT reit = (REIT) um.unmarshal(new File(path));
			Iterator<Clerk> it = reit.getStaff().getClerks().getClerk()
					.iterator();
			while (it.hasNext()) {
				Clerk tmp = it.next();
				fLogger.info(MyStringBuilder.buildString(
						"creating ClerkDetails with: name:", tmp.getName(),
						" location:", Float.toString(tmp.getLocation().getX()),
						",", Float.toString(tmp.getLocation().getY())));
				ClerkDetails tClerkDetails = new ClerkDetails(tmp.getName(), new Location(
						new Double(tmp.getLocation().getX()), new Double(tmp
								.getLocation().getY())));
				result.add(tClerkDetails);

			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	/**
	 * Creates the repair materials.
	 * @pre created JAXB classes for XML file
	 * @post returns Hashtable of RepairMaterials sorted by thier name as appear in xml
	 * @param path the path
	 * @return the hashtable
	 */
	public static Hashtable<String, RepairObjectInterface> createRepairMaterials(
			final String path) {
		fLogger.info("In CreateRepairMaterials");
		Hashtable<String, RepairObjectInterface> result = new Hashtable<String, RepairObjectInterface>();
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.initialdata.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			reit.parser.initialdata.REIT reit = (REIT) um.unmarshal(new File(path));
			Iterator<Material> it = reit.getWarehouse().getMaterials()
					.getMaterial().iterator();
			while (it.hasNext()) {
				Material tmp = it.next();
				fLogger.info(MyStringBuilder.buildString(
						"creating Material with: name:", tmp.getName(),
						" quantity:", Integer.toString(tmp.getQuantity())));
				RepairMaterial tRepairMaterial = new RepairMaterial(tmp.getName(),
						tmp.getQuantity());
				result.put(tmp.getName(), tRepairMaterial);

			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	/**
	 * Creates the repair tools.
	 * @pre created JAXB classes for XML file
	 * @post returns Hashtable of RepairTools sorted by thier name as appear in xml
	 * @param path the path
	 * @return the hashtable
	 */
	public static Hashtable<String, RepairObjectInterface> createRepairTools(
			final String path) {
		fLogger.info("In CreateRepairTools");
		Hashtable<String, RepairObjectInterface> result = new Hashtable<String, RepairObjectInterface>();
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.initialdata.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			reit.parser.initialdata.REIT reit = (REIT) um.unmarshal(new File(path));
			Iterator<Tool> it = reit.getWarehouse().getTools().getTool()
					.iterator();
			while (it.hasNext()) {
				Tool tmp = it.next();
				fLogger.info(MyStringBuilder.buildString(
						"creating Tool with: name:", tmp.getName(),
						" quantity:", Integer.toString(tmp.getQuantity())));
				RepairTool tRepairTool = new RepairTool(tmp.getName(), tmp.getQuantity());
				result.put(tmp.getName(), tRepairTool);

			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}


	/**
	 * Gets the number of maintenance persons.
	 * @pre created JAXB classes from XSD file
	 * @post returns NumberOfMaintenancePesrons as appear in xml
	 * @param path the path
	 * @return the number of maintenance persons
	 */
	public static int getNumberOfMaintenancePersons(final String path) {
		Logger logger = Logger.getLogger(Parser.class);
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.initialdata.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			reit.parser.initialdata.REIT reit = (REIT) um.unmarshal(new File(path));
			logger.info(MyStringBuilder.buildString(
					"numberOfMaintenancePersons is :", Integer.toString(reit
							.getStaff().getNumberOfMaintenancePersons())));
			return reit.getStaff().getNumberOfMaintenancePersons();
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return 0;
	}

	/**
	 * Gets the number of rental requests.
	 * @pre created JAXB classes from XSD file
	 * @post returns NumberOfRentalRequests as appear in xml
	 * @param path the path
	 * @return the number of rental requests
	 */
	public static int getNumberOfRentalRequests(final String path) {
		Logger logger = Logger.getLogger(Parser.class);
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.initialdata.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			reit.parser.initialdata.REIT reit = (REIT) um.unmarshal(new File(path));
			logger.info(MyStringBuilder.buildString(
					"numberOfRentalRequsts is :", Integer.toString(reit
							.getStaff().getTotalNumberOfRentalRequests())));
			return reit.getStaff().getTotalNumberOfRentalRequests();
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return 0;
	}

	/**
	 * Creates the customer group details.
	 * @pre created JAXB classes from XSD file
	 * @post returns Hashtable of CustomerGroupDetails as appear in xml
	 * @param path the path
	 * @return the list
	 */
	//TODO: split into seprate functions
	public static List<CustomerGroupDetails> createCustomerGroupDetails(
			final String path) {
		Logger logger = Logger.getLogger(Parser.class);
		logger.info("In CreateCustomerGroupDetails");
		List<CustomerGroupDetails> result = new ArrayList<CustomerGroupDetails>();
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.cutomergroups.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			JAXBElement<CustomersGroups> root = um.unmarshal(new StreamSource(
					path), reit.parser.cutomergroups.CustomersGroups.class);
			CustomersGroups customerGroups = root.getValue();
			Iterator<CustomerGroups.CustomerGroupDetails> it = customerGroups
					.getCustomerGroups().getCustomerGroupDetails().iterator();
			while (it.hasNext()) {
				CustomerGroups.CustomerGroupDetails tmp = it.next();
				logger.info(MyStringBuilder.buildString(
						"creating Group with: GroupManager:",
						tmp.getGroupManagerName()));

				Iterator<CustomerGroups.CustomerGroupDetails.Customers.Customer> customerIt = tmp
						.getCustomers().getCustomer().iterator();
				ArrayList<Customer> customerContainer = new ArrayList<Customer>();
				while (customerIt.hasNext()) {
					CustomerGroups.CustomerGroupDetails.Customers.Customer tmpc = customerIt
							.next();
					logger.info(MyStringBuilder.buildString(
							"creating Customer with: name:", tmpc.getName(),
							" vandlisim:", (tmpc.getVandalism()), " minDmg:",
							Float.toString(tmpc.getMinimumDamage()),
							" maxDmg:", Float.toString(tmpc.getMaximumDamage())));
					Customer toPut = new Customer(tmpc.getName(),
							tmpc.getVandalism(), tmpc.getMinimumDamage(),
							tmpc.getMaximumDamage());
					customerContainer.add(toPut);
				}
				LinkedList<RentalRequest> rentalRequestContainer = createRentalRequestsContainer(tmp);
				result.add(new CustomerGroupDetails(tmp.getGroupManagerName(),
						customerContainer, rentalRequestContainer));

			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}


	/**
	 * Creates the assets.
	 *
	 * @param path to xml file containing Assets (Assets.xml)
	 * @return the asset storage interface
	 * @pre created JAXB classes from XSD file
	 * @post returns Assets as appear in xml
	 */
	public static AssetStorageInterface createAssets(final String path) {
		Logger logger = Logger.getLogger(Parser.class);
		logger.info("In CreateAssets");
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.assets.Assets.class);
			Unmarshaller um = jc.createUnmarshaller();
			// creating a JAXBelement to store Assets no @XMLRootElement forces to do this from xml and putting value in assets
			JAXBElement<reit.parser.assets.Assets> root = um.unmarshal(
					new StreamSource(path), reit.parser.assets.Assets.class); 
			reit.parser.assets.Assets assets = root.getValue();
			return new Assets(createAssetContainer(assets));
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return null;
	}

	/**
	 * Creates the asset contents repair material details.
	 *
	 * @param path to xml file containing staff (AssetContentsRepairDetails.xml)
	 * @return the hashtable
	 * @pre created JAXB classes from XSD file
	 * @post returns Assets as appear in xml
	 */
	public static Hashtable<String, RepairMaterialInformation> createAssetContentsRepairMaterialDetails(
			final String path) {
		fLogger.info("In AssetContentsRepairMaterialDetails");
		Hashtable<String, RepairMaterialInformation> result = new Hashtable<String, RepairMaterialInformation>();
		try {
			//TODO: this is a duplicate code, please fix
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.repairinfo.AssetContentsRepairDetails.class);
			Unmarshaller um = jc.createUnmarshaller();
			// creating a JAXBelement to store Assets no @XMLRootElement forces to do this from xml and putting value in assets
			JAXBElement<reit.parser.repairinfo.AssetContentsRepairDetails> root = um
					.unmarshal(
							new StreamSource( 
									path),
									reit.parser.repairinfo.AssetContentsRepairDetails.class);
			reit.parser.repairinfo.AssetContentsRepairDetails assetContentRepairDetails = root
					.getValue();
			Iterator<reit.parser.repairinfo.AssetContentsRepairDetails.AssetContent> it = assetContentRepairDetails
					.getAssetContent().iterator();
			while (it.hasNext()) {
				reit.parser.repairinfo.AssetContentsRepairDetails.AssetContent tmp = it
						.next();
				fLogger.info(MyStringBuilder.buildString(
						"creating AssetContentRepairMatriels with: name:",
						tmp.getName()));
				RepairMaterialInformation tRepairMaterialInformation = new RepairMaterialInformation(
						tmp.getName(), tmp.getMaterials().getMaterial());
				result.put(tmp.getName(), tRepairMaterialInformation);
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	/**
	 * Creates the asset contents repair tool details.
	 *
	 * @param path to xml file containing staff (AssetContentsRepairDetails.xml)
	 * @return the hashtable
	 * @pre created JAXB classes from XSD file
	 * @post returns Assets as appear in xml
	 */
	public static Hashtable<String, RepairToolInformation> createAssetContentsRepairToolDetails(
			final String path) {
		Logger logger = Logger.getLogger(Parser.class);
		logger.info("In AssetContentsRepairToolDetails");
		Hashtable<String, RepairToolInformation> result = new Hashtable<String, RepairToolInformation>();
		try {
			JAXBContext jc = JAXBContext
					.newInstance(reit.parser.repairinfo.ObjectFactory.class);
			Unmarshaller um = jc.createUnmarshaller();
			// creating a JAXBelement to store Assets no @XMLRootElement forces to do this from xml and putting value in assets
			JAXBElement<reit.parser.repairinfo.AssetContentsRepairDetails> root = um
					.unmarshal(
							new StreamSource( 
									path),
									reit.parser.repairinfo.AssetContentsRepairDetails.class);
			reit.parser.repairinfo.AssetContentsRepairDetails assetContentRepairDetails = root
					.getValue();
			Iterator<reit.parser.repairinfo.AssetContentsRepairDetails.AssetContent> it = assetContentRepairDetails
					.getAssetContent().iterator();
			while (it.hasNext()) {
				reit.parser.repairinfo.AssetContentsRepairDetails.AssetContent tmp = it
						.next();
				logger.info(MyStringBuilder.buildString(
						"creating AssetContentRepairMatriels with: name:",
						tmp.getName()));
				RepairToolInformation tRepairToolInformation = new RepairToolInformation(
						tmp.getName(), tmp.getTools().getTool());
				result.put(tmp.getName(), tRepairToolInformation);
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	/**
	 * Creates the asset container.
	 *
	 * @param assets the assets
	 * @return the hash map
	 */
	public static HashMap<String, ArrayListMultimap<Integer, Pair<AtomicBoolean, Asset>>> createAssetContainer(
			final reit.parser.assets.Assets assets) {
		Logger logger = Logger.getLogger(Parser.class);
		Iterator<reit.parser.assets.Assets.Asset> it = assets.getAsset()
				.iterator();
		HashMap<String, ArrayListMultimap<Integer, Pair<AtomicBoolean, Asset>>> assetContainer = new HashMap<String, ArrayListMultimap<Integer, Pair<AtomicBoolean, Asset>>>();
		while (it.hasNext()) {
			reit.parser.assets.Assets.Asset tmp = it.next();
			logger.info(MyStringBuilder.buildString(
					"creating Asset with: name:", tmp.getName(), " type:",
					tmp.getType(), " location: ", tmp.getLocation().getX()
					.toString(), ","
							+ tmp.getLocation().getY().toString(), " size:",
							Integer.toString(tmp.getSize()), " cost per night:",
							Integer.toString(tmp.getCostPerNight())));
			ArrayListMultimap<String, AssetContent> assetContentContainer = createAssetContentsContainer(tmp);
			// if there is already that asset of that type in data  and that size in that type 
			if (assetContainer.containsKey(tmp.getType())
					&& assetContainer.get(tmp.getType()).containsKey(
							tmp.getSize())) {
				assetContainer
				.get(tmp.getType())
				.get((int) tmp.getSize())
				.add(new Pair<AtomicBoolean, Asset>(new AtomicBoolean(
						false), new Asset(tmp.getName(), tmp.getType(),
								new Location(new Double(tmp.getLocation()
										.getX().doubleValue()), new Double(tmp
												.getLocation().getY().doubleValue())),
												tmp.getSize(), tmp.getCostPerNight(),
												assetContentContainer)));
			} else if (assetContainer.containsKey(tmp.getType())
					&& !assetContainer.get(tmp.getType()).containsKey(
							tmp.getSize())) {
				//no key in multimap yet but type exists
				assetContainer.get(tmp.getType()).put(
						(int) tmp.getSize(),
						new Pair<AtomicBoolean, Asset>(
								new AtomicBoolean(false), new Asset(tmp
										.getName(), tmp.getType(),
										new Location(new Double(tmp
												.getLocation().getX()),
												new Double(tmp.getLocation()
														.getY())), tmp
														.getSize(), tmp
														.getCostPerNight(),
														assetContentContainer)));
			} else { 
				// no type in hasmap
				ArrayListMultimap<Integer, Pair<AtomicBoolean, Asset>> helper = ArrayListMultimap
						.create();
				helper.put(
						(int) tmp.getSize(),
						new Pair<AtomicBoolean, Asset>(
								new AtomicBoolean(false), new Asset(tmp
										.getName(), tmp.getType(),
										new Location(new Double(tmp
												.getLocation().getX()),
												new Double(tmp.getLocation()
														.getY())), tmp
														.getSize(), tmp
														.getCostPerNight(),
														assetContentContainer)));
				assetContainer.put(tmp.getType(), helper);
			}
			//FUCK YOU I PUSHED AS HARD AS I COULD EVEN GOT HEMROOHDIES
		}

		return assetContainer;
	}

	/**
	 * Creates the asset contents container.
	 *
	 * @param asset the asset
	 * @return the array list multimap
	 */
	public static ArrayListMultimap<String, AssetContent> createAssetContentsContainer(
			final reit.parser.assets.Assets.Asset asset) {
		Logger logger = Logger.getLogger(Parser.class);
		Iterator<reit.parser.assets.Assets.Asset.AssetContents.AssetContent> assetContentIt = asset
				.getAssetContents().getAssetContent().iterator();
		ArrayListMultimap<String, AssetContent> assetContentContainer = ArrayListMultimap
				.create();
		while (assetContentIt.hasNext()) {
			reit.parser.assets.Assets.Asset.AssetContents.AssetContent tmpac = assetContentIt
					.next();
			logger.info("creating AssetContent with: name:" + tmpac.getName()
					+ " RepairMultiplier:" + tmpac.getRepairMultiplier());
			AssetContent toPut = new AssetContent(tmpac.getName(),
					tmpac.getRepairMultiplier());
			assetContentContainer.put(tmpac.getName(), toPut);
		}
		return assetContentContainer;
	}

	/**
	 * Creates the rental requests container.
	 *
	 * @param customerGroups the customer groups
	 * @return the linked list
	 */
	public static LinkedList<RentalRequest> createRentalRequestsContainer(
			final CustomerGroups.CustomerGroupDetails customerGroups) {
		Logger logger = Logger.getLogger(Parser.class);
		Iterator<CustomerGroups.CustomerGroupDetails.RentalRequests.Request> rentalRequestIt = customerGroups
				.getRentalRequests().getRequest().iterator();
		LinkedList<RentalRequest> rentalRequestContainer = new LinkedList<RentalRequest>();
		while (rentalRequestIt.hasNext()) {
			CustomerGroups.CustomerGroupDetails.RentalRequests.Request tmpr = rentalRequestIt
					.next();
			logger.info("creating Customer with: id:" + tmpr.getId() + " type:"
					+ tmpr.getType() + " size:" + tmpr.getSize() + " Duration:"
					+ tmpr.getDuration());
			RentalRequest requestToPut = new RentalRequest(tmpr.getId(),
					tmpr.getType(), tmpr.getSize(), tmpr.getDuration());
			rentalRequestContainer.add(requestToPut);
		}
		return rentalRequestContainer;
	}

	/**
	 * Map as list.
	 *
	 * @param <K> the key type
	 * @param <V> the value type
	 * @param makeIntoList the make into list
	 * @return the list
	 */
	public static <K, V> List<V> mapAsList(final Map<K, V> makeIntoList) {
		List<V> result = new ArrayList<V>();
		for (K k : makeIntoList.keySet()) {
			result.add(makeIntoList.get(k));
		}

		return result;

	}
}
